package oop.ex2.main;

import java.io.IOException;
import java.util.*;
import java.util.regex.*;

import oop.ex2.tools.*;
import oop.ex2.tools.CheckType.VariableType;

/**
 * 
 * @author yossavi
 *
 */
public class Block {
	ArrayList<String> lines = new ArrayList<String>();
	ArrayList<Block> blocks = new ArrayList<Block>();
	public ArrayList<Variable> variables = new ArrayList<Variable>();
	private String type, name;
	
	/**
	 * Constructor
	 * @param type- the block type
	 * @param name- the block name
	 */
	public Block (String type,  String name) {
		this.type=type;
		this.name=name;
	}
	
	/**
	 * return the block name
	 * @return the block name
	 */
	public String getName () {
		return this.name;
	}
	
	/**
	 * return the block type
	 * @return the block type
	 */
	public String getType () {
		return this.type;
	}
	
	/**
	 * get parameter and saves it in variables
	 * @param var- the parameter to save
	 * @throws DuplicateNamesException 
	 */
	public void addParameter (Variable var) throws DuplicateNamesException {
		Sjavac.addUniqVar (var, variables);
	}
	
	/**
	 * get line and saves it in lines
	 * @param line- the line to save
	 * @throws IOException
	 */
	public void addLine (String line) throws IOException {
		lines.add(line);
	}
	
	/**
	 * run over the block body and check is correctness
	 * throws compilationException if it dosen't
	 * @throws compilationException
	 * @throws IOException
	 */
	public void checkCorrectness () throws compilationException, IOException {
		// run over the body lines
		for (int i=0; i<lines.size(); i++) {
			String line=lines.get(i);
			
			if (CheckType.isCommand(line)){
				// Removes last ";"
				line=TextProcessor.removeLast(line);
				// if there isn't return at the end of a method
				if (i==lines.size()-1 && !line.startsWith(CheckType.RETURN) && this.name!=null)
					throw new ReturnException("Must have a return statment at the end of a method");
				
				handleCommand(line);
				
			}
			else if (CheckType.isBlock(line)) {
				// Removes last "{"
				line=TextProcessor.removeLast(line);
				// if or while statement
				Pattern pattern = Pattern.compile("("+CheckType.IF+"|"+CheckType.WHILE+")\\((.+)\\)");
				Matcher matcher = pattern.matcher(line);
				if (matcher.find()) {
					if (!CheckType.isValueMuchType(matcher.group(2), VariableType.BOOLEAN.getName()))
						throw new MismatchTypeException("not boolean value");
				}
				else
					throw new UnknownStatementException("UnKnown command");
				
				Block tempBlock= new Block (this.getType(),null);
				
				boolean isCloesd=false;
				i++;
				int j=1;
				while (lines.get(i)!=null) {
					if(lines.get(i).matches(".*\\{"))
						j++;
					if (lines.get(i).matches("\\}"))
						j--;
					if (j==0) {	
						isCloesd=true;
						break;
					}
					tempBlock.addLine(lines.get(i));
					i++;
				}
				if (!isCloesd)
					throw new UnClosedStatementException("Uncloesd block");
				tempBlock.checkCorrectness();
			}
			else
				throw new UnknownStatementException("UnKnown command");
			
		}
	}
	
	/**
	 * return if the variable exist in the block
	 * @param name- of the variable to check
	 * @return true if the variable exist, false otherwise
	 */
	private boolean isExistVariable (String name) {
		boolean isExist=false;
		for (Variable v: this.variables) {
			if (v.getName().equals(name))
				isExist=true;
		}
		return isExist;
	}
	
	/**
	 * Checks if the given parameters are match the parameters type
	 * @param params- the assigned parameter to check
	 * @throws compilationException
	 */
	public void checkParamCorrectness(String params) throws compilationException {
		/*
		 * splits the params to array by "("
		 * run over the array from the end to beginning
		 * take every method, checks its type and replace it with equal value
		 * example: found method foo() with return type int, will replace it with 1
		 */
		String[] arr = params.split("\\(");
		for (int j=arr.length-1; j>=1; j--){
			
			Pattern pattern2 = Pattern.compile("(.*?)(\\))(.*)");
			Matcher matcher2 = pattern2.matcher(arr[j]);
			Pattern pattern3 = Pattern.compile("(.+,)?(.*)");
			Matcher matcher3 = pattern3.matcher(arr[j-1]);
			if (matcher2.find() && matcher3.find()) {
				String methodName= matcher3.group(2);
				String methodParameters= (matcher2.group(1)!=null)?matcher2.group(1):"";
				VariableType varType = VariableType.valueOf(CheckType.getmethodType(
						methodName+"("+methodParameters+")").toUpperCase());
				String legalReplacemnt= varType.getExmple();
				String leftFromLeft=(matcher3.group(1)!=null)?matcher3.group(1):"";
				String leftFromRight=(matcher2.group(3)!=null)?matcher2.group(3):"";
				arr[j-1]=leftFromLeft+legalReplacemnt+leftFromRight;
			}
			
		}
		params=arr[0];
		if (!params.equals("")) {
			// separate to every parameter and checks him
			Pattern pattern = Pattern.compile("((\".*?\")|('.')|([^,]+))((,?)|($))");
			Matcher matcher = pattern.matcher(params);
			boolean isMatch=matcher.find();
			int i=0;
			while (isMatch) {
				if (!CheckType.isValueMuchType(matcher.group(1),this.variables.get(i).getType()))
						throw new MismatchTypeException("Not fit method parameter");
				i++;
				isMatch=matcher.find();
			}
		}
	}
	
	/**
	 * get a line contains a command
	 * checks if it's valid and process it
	 * @param line- line with command
	 * @throws compilationException
	 */
	public void handleCommand (String line) throws compilationException {
		
		TextProcessor processedLine = new TextProcessor(line);
		String nextWord=processedLine.findNextWord();
		
		// return statement
		if (nextWord.equals(CheckType.RETURN)) {
			if (processedLine.getSeparatedSign().equals(" ")) {
				if (!CheckType.isValueMuchType(processedLine.getLine(), this.type))
					throw new MismatchTypeException("Can't return other type than method's return statment");
			}
			else if (!this.type.equals(CheckType.VOID))
				throw new MismatchTypeException("Can't return value in void method");
		}
		
		// variable statement
		else if (nextWord.equals(CheckType.FINAL) || CheckType.isVariableType(nextWord)) {
			Sjavac.addUniqVar (CorrectnessAndHandle.handleVariable(line,false), variables);
		}
		
		// Exist variable
		else if (isExistVariable(nextWord)) {
			if (processedLine.getSeparatedSign().equals("=")) {
				for (Variable v: this.variables){
					if (v.getName().equals(nextWord)) {
						String variableValue=processedLine.getLine();
							v.setValue(variableValue);
					}
				}
			}
			else
				throw new UnknownStatementException("UnKnown command");
		}
		
		// Exist member
		else if (CheckType.isExistMember(nextWord)) {
			if (processedLine.getSeparatedSign().equals("=")) {
				for (Variable m: Sjavac.members){
					if (m.getName().equals(nextWord))
						m.setValue(processedLine.getLine());
				}
			}
			else
				throw new UnknownStatementException("UnKnown command");
		}
		
		// Exist method
		else if (CheckType.isExistMethod(nextWord)) {
			CheckType.getmethodType(line);
		} 
		else
			throw new UnknownStatementException("UnKnown command");
	}
}
